#include "./CEC2010/Header.h"


#include "Self_Define_Functions.h"

#include <sys/time.h>
#include <cstdio>
#include <unistd.h>
#include <algorithm>
#include <iostream>
#include <fstream>
#include <stdlib.h>
#include <iomanip>
#include <string>
#include <boost/random.hpp>
#include <boost/random/uniform_int.hpp>
#include <boost/random/cauchy_distribution.hpp>
#include <boost/random/normal_distribution.hpp>
#include <boost/random/uniform_real.hpp>


using namespace std;


// in this program the global best solution is updated after each subgroup finish iterating itself
int main(int argc, char *argv[])
{


//////////////////////////global processing/////////////////////////////////////
    Benchmarks *fp = NULL;

    int funToRun[] = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20};  //function set
    int funNum = 20; //total function num

	/////////////////////////////////////////////////////////////////////////////////////

    int i , j, k, t, a, p;

    int counter = 0;

   /////////////////////////////////////////////

    int pre_group_num;
    int group_num ; //can be tuned properly
    int group_dim_size;
    int group_num_index;


    int group_num_max = rand_group_set[rand_group_size-1]; // the maximum number of groups

    int group_dim_size_max = dim / rand_group_set[0];//the maximum group size

    int **group_dim_index = new int*[group_num_max];//store the dimension indexes of each group
    for( i = 0; i < group_num_max; ++i )
    {
        group_dim_index[i] = new int[group_dim_size_max];
    }
    //////////////////////////////////////////////


    double *record_for_group = new double[rand_group_size];//store the relative performance improvement of each group number
    double *probablity_for_group = new double[rand_group_size];// the probability of each group number


    boost::mt19937 generator(time(0)*rand());

    boost::uniform_int<> uniform_int_group( 0, rand_group_size-1 );
    boost::variate_generator< boost::mt19937&, boost::uniform_int<> > random_int_group( generator, uniform_int_group );

    int rand_num = 1;
    int *selected_particle = new int[rand_num];

    int base_num = 2;
    int *base =new int[base_num];

    int *dim_index = new int[dim];//dimension index

    double *global_mean_position =new double[dim];//the mean postion of the swarm

    //store the index in order to shuffle
    int *population_index = new int[ Population_size ];//the indexes of particles


    int gl_best = 0;

    double **result_all_run = new double*[timesOfRun];
    for( i = 0; i < timesOfRun; ++i )
        result_all_run[i] = new double[record_set_size];

    double *final_global_best = new double[ dim ];
    double final_val;
    double pre_final_val;


    double **population = new double*[ Population_size ];//the population
    double **speed = new double*[ Population_size ];// the speed
    for( i =0; i < Population_size; ++ i )
    {
        population[i] = new double [dim];
        speed[i] = new double [dim];
    }

    double *results = new double[ Population_size ];// the fitness results for the whole population

    int *winner = new int[Population_size/2];//the winner set, also the relative good particles
    int *loser = new int[Population_size/2];// the loser set, also the relative bad particles

    int *base_set = new int[Population_size];
    int real_base_size;



    int FV = 0;



    double r1, r2, r3;

    int better_than_winner;


    for ( k=0; k<funNum; k++)
    {
        cout<<"Function "<<funToRun[k]<<" Begined!"<<endl;

        fp = generateFuncObj(funToRun[k]);
        FV = 0;

        for( i = 0; i< Population_size; ++i )
          population_index[i] = i;

        for( i = 0; i < dim; ++i )
            dim_index[i] = i;


        // to initialize the population
        boost::uniform_real<> uniform_real_generate_x( fp->getMinX(), fp->getMaxX() );
        boost::variate_generator< boost::mt19937&, boost::uniform_real<> > random_real_num_x( generator, uniform_real_generate_x );

        boost::uniform_real<> uniform_real_generate_r( 0, 1 );
        boost::variate_generator< boost::mt19937&, boost::uniform_real<> > random_real_num_r( generator, uniform_real_generate_r );



        char fun[10];
        snprintf(fun,10,"%d",funToRun[k]);
        string filename_fitness = "./Results/Fitness_result_for_function_"+ string(fun)+".txt";

        ofstream out_fitness(filename_fitness.c_str());


        if(!out_fitness)
        {
            cerr<<"Can not open the file " <<filename_fitness<<endl;
            exit(1);
        }


          for (t=0; t < timesOfRun; t++)
          {
                cout<<"Running the "<<t<<"th times"<<endl;

                FV = 0;
                counter = 0;

                // initialize the population
                for( i =0 ; i < Population_size; ++i )
                    for( j =0; j < dim; ++j )
                    {
                        population[i][j] = random_real_num_x();
                        speed[i][j] = 0;
                    }

                //calculate the fitness of the swarm
                Fitness_Computation( results, population, gl_best, Population_size, dim, FV, fp );

                pre_final_val = final_val = results[gl_best];

                memcpy( final_global_best, population[gl_best], sizeof(double)*dim );

                pre_group_num = group_num = 0;

                for( i = 0; i < rand_group_size; ++i )
                {
                    record_for_group[i] = 1;
                }




                while( FV < MAX_FV )
                {

                    if( FV >= record_FV_set[counter] )
                    {
                        result_all_run[t][counter] = final_val;
                        counter++;
                    }

                    //compute the probability of each group number
                    Compute_Probablity( record_for_group, probablity_for_group, rand_group_size );

                    // shuffle the permutation of all population
                    random_shuffle( population_index, population_index + Population_size );

                    // compute the weighted mean position of the swarm
                    Weighted_Global_Mean_Position( population, results, global_mean_position, Population_size, dim );


                    //randomly select a group number
                    group_num_index =  Select_Group_Size(  probablity_for_group, rand_group_size );
                    group_num = rand_group_set[group_num_index];
                    group_dim_size = dim /group_num;


                    for( i = 0, j = 0; i < Population_size; i += 2, ++j )
                    {
                        // pairwise competition
                        if( results[population_index[i]] < results[population_index[i+1]] )
                        {
                            winner[j] = population_index[i];
                            loser[j] = population_index[i+1];
                        }
                        else
                        {
                            winner[j] = population_index[i+1];
                            loser[j] = population_index[i];
                        }
                    }

                    Update_Base_Set( results, winner, Population_size/2, loser, Population_size/2, base_set, real_base_size );

                    //update the losers
                    for( i = 0; i < Population_size/2; ++i )
                    {
                        //randomly permutate the dimensions of each loser
                        random_shuffle( dim_index, dim_index + dim );

                        //randomly partition the dimensions into groups
                        for( j = 0, p = 0; j < group_num; ++j )
                            for( a = 0; a < group_dim_size; ++a )
                                group_dim_index[j][a] = dim_index[p++];

                        base[0] = loser[i];
                        base[1] = winner[i];

                        //update dimensions in each group
                        for( j =0; j < group_num; ++j )
                        {
                            //randomly select a relatively good particle from RG
                            Random_Diff_Number( base, base_num, real_base_size, selected_particle, rand_num );

                            better_than_winner = winner[i];

                            //compare the selected exemplar with the loser's winner
                            if( results[base_set[selected_particle[0]]] < results[winner[i]] )
                                better_than_winner = base_set[selected_particle[0]];

                            //update the dimension of this group
                            for( a = 0; a < group_dim_size; ++a )
                            {
                                r1 = random_real_num_r();
                                r2 = random_real_num_r();
                                r3 = random_real_num_r();

                                //speed update
                                speed[loser[i]][group_dim_index[j][a]] = r1 * speed[loser[i]][group_dim_index[j][a]]
                                                                       + r2* ( population[better_than_winner][group_dim_index[j][a]] - population[loser[i]][group_dim_index[j][a]] ) +
                                                                       + r3 * phi * ( global_mean_position[group_dim_index[j][a]]  - population[loser[i]][group_dim_index[j][a]] );
                                //position update
                                population[loser[i]][group_dim_index[j][a]] += speed[loser[i]][group_dim_index[j][a]];


                                //rectify the out-of-bound dimensions
                                if( population[loser[i]][group_dim_index[j][a]] < fp->getMinX() )
                                {
                                    population[loser[i]][group_dim_index[j][a]] = fp->getMinX();
                                }

                                if( population[loser[i]][group_dim_index[j][a]] > fp->getMaxX() )
                                {
                                    population[loser[i]][group_dim_index[j][a]] = fp->getMaxX();
                                }
                            }



                        }

                        //calculate the fitness of the updated loser
                        Fitness( results[loser[i]], population[loser[i]], FV, fp );

                        if( results[loser[i]] < final_val )
                        {

                            final_val = results[loser[i]];
                            memcpy( final_global_best, population[loser[i]], sizeof(double)*dim );


                        }

                    }

                    //compute the relative performance improvement
                    record_for_group[group_num_index] = Compute_Relative_Performance( pre_final_val, final_val );

                    pre_final_val = final_val;

                }


            result_all_run[t][counter] = final_val;


            cout<<"The result is "<<final_val<<endl;


        }

        //write the results into files
        for( i = 0; i < timesOfRun; ++i )
        {
            for( j = 0; j < record_set_size; ++j )
                out_fitness<<result_all_run[i][j]<<"\t";

            out_fitness<<endl;
        }


        out_fitness.close();

        cout<<"Function "<<funToRun[k]<<" Finished!"<<endl;



	}


	//release the resouces
    for( i =0; i < Population_size; ++ i )
    {
        delete []population[i];

        delete []speed[i];
    }
    delete []population;
    delete []speed;


    delete []population_index;
    delete []dim_index;
    for( i = 0; i < timesOfRun; ++i )
        delete []result_all_run[i];
    delete []result_all_run;
    delete []final_global_best;
    delete []results;
    delete []selected_particle;
    delete []base;
    delete []global_mean_position;

    for( i = 0; i < group_num_max; ++i )
    {
        delete []group_dim_index[i];
    }

    delete []group_dim_index;


    return 0;
}



